import { SetStateAction, useCallback, useRef, useState } from 'react';

import { useRouter } from 'next/router';
import { useIsomorphicLayoutEffect } from 'usehooks-ts';

const defaultDeserialize = <Value>(v: string | null) => v as unknown as Value;
const defaultSerialize = String;
function useEvent<T extends (...args: any) => any>(fn: T) {
  const fnRef = useRef(fn);
  useIsomorphicLayoutEffect(() => {
    fnRef.current = fn;
  }, [fn]);

  const eventCb = useCallback((...args: unknown[]) => fnRef.current.apply(null, args), [fnRef]);

  return eventCb as unknown as T;
}

function setSearchParam(search: string, param: string, value: string) {
  const searchParams = new URLSearchParams(search);
  searchParams.set(param, value);
  return searchParams.toString();
}
function getSearchParam(search: string, param: string) {
  const searchParams = new URLSearchParams(search);
  return searchParams.get(param);
}
function isFunction(value: any): value is (...args: any) => any {
  return typeof value === 'function';
}

interface UseSearchParamsStateOptions<Value> {
  key: string;
  serialize?: (value: Value) => string;
  deserialize?: (value: string | null) => Value;
}
export const useParams = <Value>({
  key,
  serialize = defaultSerialize,
  deserialize = defaultDeserialize,
}: UseSearchParamsStateOptions<Value>) => {
  const router = useRouter();

  const searchParam = getSearchParam(router.asPath.split('?')[1], key);
  const [value, setValue] = useState(() => deserialize(searchParam));
  const updateValue = useEvent((newValue: SetStateAction<Value>) => {
    const search: string = window?.location?.search;

    const actualNewValue: Value = isFunction(newValue) ? newValue(value) : newValue;
    setValue(actualNewValue);
    const newSearch = setSearchParam(search, key, serialize(actualNewValue));
    router.replace(`?${newSearch}`, undefined, { scroll: false });
  });

  return [value, updateValue] as const;
};
